/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing.plaf.synth;

import java.awt.*;
import java.beans.*;
import java.io.*;
import java.lang.ref.*;
import java.net.*;
import java.security.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;

import sun.awt.*;
import sun.security.action.*;
import sun.swing.*;
import sun.swing.plaf.synth.*;

/**
 * SynthLookAndFeel provides the basis for creating a customized look and
 * feel. SynthLookAndFeel does not directly provide a look, all painting is
 * delegated.
 * You need to either provide a configuration file, by way of the
 * {@link #load} method, or provide your own {@link SynthStyleFactory}
 * to {@link #setStyleFactory}. Refer to the
 * <a href="package-summary.html">package summary</a> for an example of
 * loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for
 * an example of providing your own <code>SynthStyleFactory</code> to
 * <code>setStyleFactory</code>.
 * <p>
 * <strong>Warning:</strong>
 * This class implements {@link Serializable} as a side effect of it
 * extending {@link BasicLookAndFeel}. It is not intended to be serialized.
 * An attempt to serialize it will
 * result in {@link NotSerializableException}.
 *
 * @author Scott Violet
 * @serial exclude
 * @since 1.5
 */
public class SynthLookAndFeel extends BasicLookAndFeel {

  /**
   * Used in a handful of places where we need an empty Insets.
   */
  static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource(
      0, 0, 0, 0);

  /**
   * AppContext key to get the current SynthStyleFactory.
   */
  private static final Object STYLE_FACTORY_KEY =
      new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache");

  /**
   * AppContext key to get selectedUI.
   */
  private static final Object SELECTED_UI_KEY = new StringBuilder("selectedUI");

  /**
   * AppContext key to get selectedUIState.
   */
  private static final Object SELECTED_UI_STATE_KEY = new StringBuilder("selectedUIState");

  /**
   * The last SynthStyleFactory that was asked for from AppContext
   * <code>lastContext</code>.
   */
  private static SynthStyleFactory lastFactory;
  /**
   * AppContext lastLAF came from.
   */
  private static AppContext lastContext;

  /**
   * SynthStyleFactory for the this SynthLookAndFeel.
   */
  private SynthStyleFactory factory;

  /**
   * Map of defaults table entries. This is populated via the load
   * method.
   */
  private Map<String, Object> defaultsMap;

  private Handler _handler;

  static ComponentUI getSelectedUI() {
    return (ComponentUI) AppContext.getAppContext().get(SELECTED_UI_KEY);
  }

  /**
   * Used by the renderers. For the most part the renderers are implemented
   * as Labels, which is problematic in so far as they are never selected.
   * To accommodate this SynthLabelUI checks if the current
   * UI matches that of <code>selectedUI</code> (which this methods sets), if
   * it does, then a state as set by this method is returned. This provides
   * a way for labels to have a state other than selected.
   */
  static void setSelectedUI(ComponentUI uix, boolean selected,
      boolean focused, boolean enabled,
      boolean rollover) {
    int selectedUIState = 0;

    if (selected) {
      selectedUIState = SynthConstants.SELECTED;
      if (focused) {
        selectedUIState |= SynthConstants.FOCUSED;
      }
    } else if (rollover && enabled) {
      selectedUIState |=
          SynthConstants.MOUSE_OVER | SynthConstants.ENABLED;
      if (focused) {
        selectedUIState |= SynthConstants.FOCUSED;
      }
    } else {
      if (enabled) {
        selectedUIState |= SynthConstants.ENABLED;
        if (focused) {
          selectedUIState |= SynthConstants.FOCUSED;
        }
      } else {
        selectedUIState |= SynthConstants.DISABLED;
      }
    }

    AppContext context = AppContext.getAppContext();

    context.put(SELECTED_UI_KEY, uix);
    context.put(SELECTED_UI_STATE_KEY, Integer.valueOf(selectedUIState));
  }

  static int getSelectedUIState() {
    Integer result = (Integer) AppContext.getAppContext().get(SELECTED_UI_STATE_KEY);

    return result == null ? 0 : result.intValue();
  }

  /**
   * Clears out the selected UI that was last set in setSelectedUI.
   */
  static void resetSelectedUI() {
    AppContext.getAppContext().remove(SELECTED_UI_KEY);
  }


  /**
   * Sets the SynthStyleFactory that the UI classes provided by
   * synth will use to obtain a SynthStyle.
   *
   * @param cache SynthStyleFactory the UIs should use.
   */
  public static void setStyleFactory(SynthStyleFactory cache) {
    // We assume the setter is called BEFORE the getter has been invoked
    // for a particular AppContext.
    synchronized (SynthLookAndFeel.class) {
      AppContext context = AppContext.getAppContext();
      lastFactory = cache;
      lastContext = context;
      context.put(STYLE_FACTORY_KEY, cache);
    }
  }

  /**
   * Returns the current SynthStyleFactory.
   *
   * @return SynthStyleFactory
   */
  public static SynthStyleFactory getStyleFactory() {
    synchronized (SynthLookAndFeel.class) {
      AppContext context = AppContext.getAppContext();

      if (lastContext == context) {
        return lastFactory;
      }
      lastContext = context;
      lastFactory = (SynthStyleFactory) context.get(STYLE_FACTORY_KEY);
      return lastFactory;
    }
  }

  /**
   * Returns the component state for the specified component. This should
   * only be used for Components that don't have any special state beyond
   * that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't
   * call into this method.
   */
  static int getComponentState(Component c) {
    if (c.isEnabled()) {
      if (c.isFocusOwner()) {
        return SynthUI.ENABLED | SynthUI.FOCUSED;
      }
      return SynthUI.ENABLED;
    }
    return SynthUI.DISABLED;
  }

  /**
   * Gets a SynthStyle for the specified region of the specified component.
   * This is not for general consumption, only custom UIs should call this
   * method.
   *
   * @param c JComponent to get the SynthStyle for
   * @param region Identifies the region of the specified component
   * @return SynthStyle to use.
   */
  public static SynthStyle getStyle(JComponent c, Region region) {
    return getStyleFactory().getStyle(c, region);
  }

  /**
   * Returns true if the Style should be updated in response to the
   * specified PropertyChangeEvent. This forwards to
   * <code>shouldUpdateStyleOnAncestorChanged</code> as necessary.
   */
  static boolean shouldUpdateStyle(PropertyChangeEvent event) {
    LookAndFeel laf = UIManager.getLookAndFeel();
    return (laf instanceof SynthLookAndFeel &&
        ((SynthLookAndFeel) laf).shouldUpdateStyleOnEvent(event));
  }

  /**
   * A convience method that will reset the Style of StyleContext if
   * necessary.
   *
   * @return newStyle
   */
  static SynthStyle updateStyle(SynthContext context, SynthUI ui) {
    SynthStyle newStyle = getStyle(context.getComponent(),
        context.getRegion());
    SynthStyle oldStyle = context.getStyle();

    if (newStyle != oldStyle) {
      if (oldStyle != null) {
        oldStyle.uninstallDefaults(context);
      }
      context.setStyle(newStyle);
      newStyle.installDefaults(context, ui);
    }
    return newStyle;
  }

  /**
   * Updates the style associated with <code>c</code>, and all its children.
   * This is a lighter version of
   * <code>SwingUtilities.updateComponentTreeUI</code>.
   *
   * @param c Component to update style for.
   */
  public static void updateStyles(Component c) {
    if (c instanceof JComponent) {
      // Yes, this is hacky. A better solution is to get the UI
      // and cast, but JComponent doesn't expose a getter for the UI
      // (each of the UIs do), making that approach impractical.
      String name = c.getName();
      c.setName(null);
      if (name != null) {
        c.setName(name);
      }
      ((JComponent) c).revalidate();
    }
    Component[] children = null;
    if (c instanceof JMenu) {
      children = ((JMenu) c).getMenuComponents();
    } else if (c instanceof Container) {
      children = ((Container) c).getComponents();
    }
    if (children != null) {
      for (Component child : children) {
        updateStyles(child);
      }
    }
    c.repaint();
  }

  /**
   * Returns the Region for the JComponent <code>c</code>.
   *
   * @param c JComponent to fetch the Region for
   * @return Region corresponding to <code>c</code>
   */
  public static Region getRegion(JComponent c) {
    return Region.getRegion(c);
  }

  /**
   * A convenience method to return where the foreground should be
   * painted for the Component identified by the passed in
   * AbstractSynthContext.
   */
  static Insets getPaintingInsets(SynthContext state, Insets insets) {
    if (state.isSubregion()) {
      insets = state.getStyle().getInsets(state, insets);
    } else {
      insets = state.getComponent().getInsets(insets);
    }
    return insets;
  }

  /**
   * A convenience method that handles painting of the background.
   * All SynthUI implementations should override update and invoke
   * this method.
   */
  static void update(SynthContext state, Graphics g) {
    paintRegion(state, g, null);
  }

  /**
   * A convenience method that handles painting of the background for
   * subregions. All SynthUI's that have subregions should invoke
   * this method, than paint the foreground.
   */
  static void updateSubregion(SynthContext state, Graphics g,
      Rectangle bounds) {
    paintRegion(state, g, bounds);
  }

  private static void paintRegion(SynthContext state, Graphics g,
      Rectangle bounds) {
    JComponent c = state.getComponent();
    SynthStyle style = state.getStyle();
    int x, y, width, height;

    if (bounds == null) {
      x = 0;
      y = 0;
      width = c.getWidth();
      height = c.getHeight();
    } else {
      x = bounds.x;
      y = bounds.y;
      width = bounds.width;
      height = bounds.height;
    }

    // Fill in the background, if necessary.
    boolean subregion = state.isSubregion();
    if ((subregion && style.isOpaque(state)) ||
        (!subregion && c.isOpaque())) {
      g.setColor(style.getColor(state, ColorType.BACKGROUND));
      g.fillRect(x, y, width, height);
    }
  }

  static boolean isLeftToRight(Component c) {
    return c.getComponentOrientation().isLeftToRight();
  }

  /**
   * Returns the ui that is of type <code>klass</code>, or null if
   * one can not be found.
   */
  static Object getUIOfType(ComponentUI ui, Class klass) {
    if (klass.isInstance(ui)) {
      return ui;
    }
    return null;
  }

  /**
   * Creates the Synth look and feel <code>ComponentUI</code> for
   * the passed in <code>JComponent</code>.
   *
   * @param c JComponent to create the <code>ComponentUI</code> for
   * @return ComponentUI to use for <code>c</code>
   */
  public static ComponentUI createUI(JComponent c) {
    String key = c.getUIClassID().intern();

    if (key == "ButtonUI") {
      return SynthButtonUI.createUI(c);
    } else if (key == "CheckBoxUI") {
      return SynthCheckBoxUI.createUI(c);
    } else if (key == "CheckBoxMenuItemUI") {
      return SynthCheckBoxMenuItemUI.createUI(c);
    } else if (key == "ColorChooserUI") {
      return SynthColorChooserUI.createUI(c);
    } else if (key == "ComboBoxUI") {
      return SynthComboBoxUI.createUI(c);
    } else if (key == "DesktopPaneUI") {
      return SynthDesktopPaneUI.createUI(c);
    } else if (key == "DesktopIconUI") {
      return SynthDesktopIconUI.createUI(c);
    } else if (key == "EditorPaneUI") {
      return SynthEditorPaneUI.createUI(c);
    } else if (key == "FileChooserUI") {
      return SynthFileChooserUI.createUI(c);
    } else if (key == "FormattedTextFieldUI") {
      return SynthFormattedTextFieldUI.createUI(c);
    } else if (key == "InternalFrameUI") {
      return SynthInternalFrameUI.createUI(c);
    } else if (key == "LabelUI") {
      return SynthLabelUI.createUI(c);
    } else if (key == "ListUI") {
      return SynthListUI.createUI(c);
    } else if (key == "MenuBarUI") {
      return SynthMenuBarUI.createUI(c);
    } else if (key == "MenuUI") {
      return SynthMenuUI.createUI(c);
    } else if (key == "MenuItemUI") {
      return SynthMenuItemUI.createUI(c);
    } else if (key == "OptionPaneUI") {
      return SynthOptionPaneUI.createUI(c);
    } else if (key == "PanelUI") {
      return SynthPanelUI.createUI(c);
    } else if (key == "PasswordFieldUI") {
      return SynthPasswordFieldUI.createUI(c);
    } else if (key == "PopupMenuSeparatorUI") {
      return SynthSeparatorUI.createUI(c);
    } else if (key == "PopupMenuUI") {
      return SynthPopupMenuUI.createUI(c);
    } else if (key == "ProgressBarUI") {
      return SynthProgressBarUI.createUI(c);
    } else if (key == "RadioButtonUI") {
      return SynthRadioButtonUI.createUI(c);
    } else if (key == "RadioButtonMenuItemUI") {
      return SynthRadioButtonMenuItemUI.createUI(c);
    } else if (key == "RootPaneUI") {
      return SynthRootPaneUI.createUI(c);
    } else if (key == "ScrollBarUI") {
      return SynthScrollBarUI.createUI(c);
    } else if (key == "ScrollPaneUI") {
      return SynthScrollPaneUI.createUI(c);
    } else if (key == "SeparatorUI") {
      return SynthSeparatorUI.createUI(c);
    } else if (key == "SliderUI") {
      return SynthSliderUI.createUI(c);
    } else if (key == "SpinnerUI") {
      return SynthSpinnerUI.createUI(c);
    } else if (key == "SplitPaneUI") {
      return SynthSplitPaneUI.createUI(c);
    } else if (key == "TabbedPaneUI") {
      return SynthTabbedPaneUI.createUI(c);
    } else if (key == "TableUI") {
      return SynthTableUI.createUI(c);
    } else if (key == "TableHeaderUI") {
      return SynthTableHeaderUI.createUI(c);
    } else if (key == "TextAreaUI") {
      return SynthTextAreaUI.createUI(c);
    } else if (key == "TextFieldUI") {
      return SynthTextFieldUI.createUI(c);
    } else if (key == "TextPaneUI") {
      return SynthTextPaneUI.createUI(c);
    } else if (key == "ToggleButtonUI") {
      return SynthToggleButtonUI.createUI(c);
    } else if (key == "ToolBarSeparatorUI") {
      return SynthSeparatorUI.createUI(c);
    } else if (key == "ToolBarUI") {
      return SynthToolBarUI.createUI(c);
    } else if (key == "ToolTipUI") {
      return SynthToolTipUI.createUI(c);
    } else if (key == "TreeUI") {
      return SynthTreeUI.createUI(c);
    } else if (key == "ViewportUI") {
      return SynthViewportUI.createUI(c);
    }
    return null;
  }


  /**
   * Creates a SynthLookAndFeel.
   * <p>
   * For the returned <code>SynthLookAndFeel</code> to be useful you need to
   * invoke <code>load</code> to specify the set of
   * <code>SynthStyle</code>s, or invoke <code>setStyleFactory</code>.
   *
   * @see #load
   * @see #setStyleFactory
   */
  public SynthLookAndFeel() {
    factory = new DefaultSynthStyleFactory();
    _handler = new Handler();
  }

  /**
   * Loads the set of <code>SynthStyle</code>s that will be used by
   * this <code>SynthLookAndFeel</code>. <code>resourceBase</code> is
   * used to resolve any path based resources, for example an
   * <code>Image</code> would be resolved by
   * <code>resourceBase.getResource(path)</code>. Refer to
   * <a href="doc-files/synthFileFormat.html">Synth File Format</a>
   * for more information.
   *
   * @param input InputStream to load from
   * @param resourceBase used to resolve any images or other resources
   * @throws ParseException if there is an error in parsing
   * @throws IllegalArgumentException if input or resourceBase is <code>null</code>
   */
  public void load(InputStream input, Class<?> resourceBase) throws
      ParseException {
    if (resourceBase == null) {
      throw new IllegalArgumentException(
          "You must supply a valid resource base Class");
    }

    if (defaultsMap == null) {
      defaultsMap = new HashMap<String, Object>();
    }

    new SynthParser().parse(input, (DefaultSynthStyleFactory) factory,
        null, resourceBase, defaultsMap);
  }

  /**
   * Loads the set of <code>SynthStyle</code>s that will be used by
   * this <code>SynthLookAndFeel</code>. Path based resources are resolved
   * relatively to the specified <code>URL</code> of the style. For example
   * an <code>Image</code> would be resolved by
   * <code>new URL(synthFile, path)</code>. Refer to
   * <a href="doc-files/synthFileFormat.html">Synth File Format</a> for more
   * information.
   *
   * @param url the <code>URL</code> to load the set of <code>SynthStyle</code> from
   * @throws ParseException if there is an error in parsing
   * @throws IllegalArgumentException if synthSet is <code>null</code>
   * @throws IOException if synthSet cannot be opened as an <code>InputStream</code>
   * @since 1.6
   */
  public void load(URL url) throws ParseException, IOException {
    if (url == null) {
      throw new IllegalArgumentException(
          "You must supply a valid Synth set URL");
    }

    if (defaultsMap == null) {
      defaultsMap = new HashMap<String, Object>();
    }

    InputStream input = url.openStream();
    new SynthParser().parse(input, (DefaultSynthStyleFactory) factory,
        url, null, defaultsMap);
  }

  /**
   * Called by UIManager when this look and feel is installed.
   */
  @Override
  public void initialize() {
    super.initialize();
    DefaultLookup.setDefaultLookup(new SynthDefaultLookup());
    setStyleFactory(factory);
    KeyboardFocusManager.getCurrentKeyboardFocusManager().
        addPropertyChangeListener(_handler);
  }

  /**
   * Called by UIManager when this look and feel is uninstalled.
   */
  @Override
  public void uninitialize() {
    KeyboardFocusManager.getCurrentKeyboardFocusManager().
        removePropertyChangeListener(_handler);
    // We should uninstall the StyleFactory here, but unfortunately
    // there are a handful of things that retain references to the
    // LookAndFeel and expect things to work
    super.uninitialize();
  }

  /**
   * Returns the defaults for this SynthLookAndFeel.
   *
   * @return Defaults table.
   */
  @Override
  public UIDefaults getDefaults() {
    UIDefaults table = new UIDefaults(60, 0.75f);

    Region.registerUIs(table);
    table.setDefaultLocale(Locale.getDefault());
    table.addResourceBundle(
        "com.sun.swing.internal.plaf.basic.resources.basic");
    table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth");

    // SynthTabbedPaneUI supports rollover on tabs, GTK does not
    table.put("TabbedPane.isTabRollover", Boolean.TRUE);

    // These need to be defined for JColorChooser to work.
    table.put("ColorChooser.swatchesRecentSwatchSize",
        new Dimension(10, 10));
    table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED);
    table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10));

    // These need to be defined for ImageView.
    table.put("html.pendingImage", SwingUtilities2.makeIcon(getClass(),
        BasicLookAndFeel.class,
        "icons/image-delayed.png"));
    table.put("html.missingImage", SwingUtilities2.makeIcon(getClass(),
        BasicLookAndFeel.class,
        "icons/image-failed.png"));

    // These are needed for PopupMenu.
    table.put("PopupMenu.selectedWindowInputMapBindings", new Object[]{
        "ESCAPE", "cancel",
        "DOWN", "selectNext",
        "KP_DOWN", "selectNext",
        "UP", "selectPrevious",
        "KP_UP", "selectPrevious",
        "LEFT", "selectParent",
        "KP_LEFT", "selectParent",
        "RIGHT", "selectChild",
        "KP_RIGHT", "selectChild",
        "ENTER", "return",
        "SPACE", "return"
    });
    table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft",
        new Object[]{
            "LEFT", "selectChild",
            "KP_LEFT", "selectChild",
            "RIGHT", "selectParent",
            "KP_RIGHT", "selectParent",
        });

    // enabled antialiasing depending on desktop settings
    flushUnreferenced();
    Object aaTextInfo = getAATextInfo();
    table.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo);
    new AATextListener(this);

    if (defaultsMap != null) {
      table.putAll(defaultsMap);
    }
    return table;
  }

  /**
   * Returns true, SynthLookAndFeel is always supported.
   *
   * @return true.
   */
  @Override
  public boolean isSupportedLookAndFeel() {
    return true;
  }

  /**
   * Returns false, SynthLookAndFeel is not a native look and feel.
   *
   * @return false
   */
  @Override
  public boolean isNativeLookAndFeel() {
    return false;
  }

  /**
   * Returns a textual description of SynthLookAndFeel.
   *
   * @return textual description of synth.
   */
  @Override
  public String getDescription() {
    return "Synth look and feel";
  }

  /**
   * Return a short string that identifies this look and feel.
   *
   * @return a short string identifying this look and feel.
   */
  @Override
  public String getName() {
    return "Synth look and feel";
  }

  /**
   * Return a string that identifies this look and feel.
   *
   * @return a short string identifying this look and feel.
   */
  @Override
  public String getID() {
    return "Synth";
  }

  /**
   * Returns whether or not the UIs should update their
   * <code>SynthStyles</code> from the <code>SynthStyleFactory</code>
   * when the ancestor of the <code>JComponent</code> changes. A subclass
   * that provided a <code>SynthStyleFactory</code> that based the
   * return value from <code>getStyle</code> off the containment hierarchy
   * would override this method to return true.
   *
   * @return whether or not the UIs should update their <code>SynthStyles</code> from the
   * <code>SynthStyleFactory</code> when the ancestor changed.
   */
  public boolean shouldUpdateStyleOnAncestorChanged() {
    return false;
  }

  /**
   * Returns whether or not the UIs should update their styles when a
   * particular event occurs.
   *
   * @param ev a {@code PropertyChangeEvent}
   * @return whether or not the UIs should update their styles
   * @since 1.7
   */
  protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
    String eName = ev.getPropertyName();
    if ("name" == eName || "componentOrientation" == eName) {
      return true;
    }
    if ("ancestor" == eName && ev.getNewValue() != null) {
      // Only update on an ancestor change when getting a valid
      // parent and the LookAndFeel wants this.
      return shouldUpdateStyleOnAncestorChanged();
    }
    return false;
  }

  /**
   * Returns the antialiasing information as specified by the host desktop.
   * Antialiasing might be forced off if the desktop is GNOME and the user
   * has set his locale to Chinese, Japanese or Korean. This is consistent
   * with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel
   * for more information about CJK and antialiased fonts.
   *
   * @return the text antialiasing information associated to the desktop
   */
  private static Object getAATextInfo() {
    String language = Locale.getDefault().getLanguage();
    String desktop =
        AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));

    boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) ||
        Locale.JAPANESE.getLanguage().equals(language) ||
        Locale.KOREAN.getLanguage().equals(language));
    boolean isGnome = "gnome".equals(desktop);
    boolean isLocal = SwingUtilities2.isLocalDisplay();

    boolean setAA = isLocal && (!isGnome || !isCjkLocale);

    Object aaTextInfo = SwingUtilities2.AATextInfo.getAATextInfo(setAA);
    return aaTextInfo;
  }

  private static ReferenceQueue<LookAndFeel> queue = new ReferenceQueue<LookAndFeel>();

  private static void flushUnreferenced() {
    AATextListener aatl;
    while ((aatl = (AATextListener) queue.poll()) != null) {
      aatl.dispose();
    }
  }

  private static class AATextListener
      extends WeakReference<LookAndFeel> implements PropertyChangeListener {

    private String key = SunToolkit.DESKTOPFONTHINTS;

    AATextListener(LookAndFeel laf) {
      super(laf, queue);
      Toolkit tk = Toolkit.getDefaultToolkit();
      tk.addPropertyChangeListener(key, this);
    }

    @Override
    public void propertyChange(PropertyChangeEvent pce) {
      UIDefaults defaults = UIManager.getLookAndFeelDefaults();
      if (defaults.getBoolean("Synth.doNotSetTextAA")) {
        dispose();
        return;
      }

      LookAndFeel laf = get();
      if (laf == null || laf != UIManager.getLookAndFeel()) {
        dispose();
        return;
      }

      Object aaTextInfo = getAATextInfo();
      defaults.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo);

      updateUI();
    }

    void dispose() {
      Toolkit tk = Toolkit.getDefaultToolkit();
      tk.removePropertyChangeListener(key, this);
    }

    /**
     * Updates the UI of the passed in window and all its children.
     */
    private static void updateWindowUI(Window window) {
      updateStyles(window);
      Window ownedWins[] = window.getOwnedWindows();
      for (Window w : ownedWins) {
        updateWindowUI(w);
      }
    }

    /**
     * Updates the UIs of all the known Frames.
     */
    private static void updateAllUIs() {
      Frame appFrames[] = Frame.getFrames();
      for (Frame frame : appFrames) {
        updateWindowUI(frame);
      }
    }

    /**
     * Indicates if an updateUI call is pending.
     */
    private static boolean updatePending;

    /**
     * Sets whether or not an updateUI call is pending.
     */
    private static synchronized void setUpdatePending(boolean update) {
      updatePending = update;
    }

    /**
     * Returns true if a UI update is pending.
     */
    private static synchronized boolean isUpdatePending() {
      return updatePending;
    }

    protected void updateUI() {
      if (!isUpdatePending()) {
        setUpdatePending(true);
        Runnable uiUpdater = new Runnable() {
          @Override
          public void run() {
            updateAllUIs();
            setUpdatePending(false);
          }
        };
        SwingUtilities.invokeLater(uiUpdater);
      }
    }
  }

  private void writeObject(java.io.ObjectOutputStream out)
      throws IOException {
    throw new NotSerializableException(this.getClass().getName());
  }

  private class Handler implements PropertyChangeListener {

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
      String propertyName = evt.getPropertyName();
      Object newValue = evt.getNewValue();
      Object oldValue = evt.getOldValue();

      if ("focusOwner" == propertyName) {
        if (oldValue instanceof JComponent) {
          repaintIfBackgroundsDiffer((JComponent) oldValue);

        }

        if (newValue instanceof JComponent) {
          repaintIfBackgroundsDiffer((JComponent) newValue);
        }
      } else if ("managingFocus" == propertyName) {
        // De-register listener on old keyboard focus manager and
        // register it on the new one.
        KeyboardFocusManager manager =
            (KeyboardFocusManager) evt.getSource();
        if (newValue.equals(Boolean.FALSE)) {
          manager.removePropertyChangeListener(_handler);
        } else {
          manager.addPropertyChangeListener(_handler);
        }
      }
    }

    /**
     * This is a support method that will check if the background colors of
     * the specified component differ between focused and unfocused states.
     * If the color differ the component will then repaint itself.
     *
     * @comp the component to check
     */
    private void repaintIfBackgroundsDiffer(JComponent comp) {
      ComponentUI ui = (ComponentUI) comp.getClientProperty(
          SwingUtilities2.COMPONENT_UI_PROPERTY_KEY);
      if (ui instanceof SynthUI) {
        SynthUI synthUI = (SynthUI) ui;
        SynthContext context = synthUI.getContext(comp);
        SynthStyle style = context.getStyle();
        int state = context.getComponentState();

        // Get the current background color.
        Color currBG = style.getColor(context, ColorType.BACKGROUND);

        // Get the last background color.
        state ^= SynthConstants.FOCUSED;
        context.setComponentState(state);
        Color lastBG = style.getColor(context, ColorType.BACKGROUND);

        // Reset the component state back to original.
        state ^= SynthConstants.FOCUSED;
        context.setComponentState(state);

        // Repaint the component if the backgrounds differed.
        if (currBG != null && !currBG.equals(lastBG)) {
          comp.repaint();
        }
        context.dispose();
      }
    }
  }
}
